/**
 * rcat - An cat implemented by Renê =:)
 *
 * Author: Renê de Souza Pinto < rene@grad.icmc.usp.br >
 * Date: Mar, 2009
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define VERSION 	"1.0"
#define BUG_REPORTS "rene@grad.icmc.usp.br"

#define BUFFER_SIZE 4096
#define OPTS_SIZE	7
#define NONB		0
#define NUM			1
#define SQU			2
#define SHOWT		3
#define SHOWNP		4
#define SHOWENDS    5
#define NONV		6


/* prototypes */
unsigned int read_and_print(int fdin,  int fdout, char opts[], unsigned int lines);
void cprint(const char *format, ...);
void show_help(char **argv);


/* main */
int main(int argc, char **argv)
{
	int fdin, fdout;
	unsigned int i, lines;
	char opts[OPTS_SIZE];
	static struct option longOpts[] = {
		{"show-all",         no_argument, NULL, 'A'},
		{"number-nonblank",  no_argument, NULL, 'b'},
		{"show-ends",        no_argument, NULL, 'E'},
		{"number",           no_argument, NULL, 'n'},
		{"squeeze-blank",    no_argument, NULL, 's'},
		{"show-tabs",        no_argument, NULL, 'T'},
		{"show-nonprinting", no_argument, NULL, 'v'},
		{"help",             no_argument, NULL, 'h'},
		{"version",          no_argument, NULL, 'i'},
		{NULL, no_argument, NULL, 0}
	};
	const char *optstring = "AbEnsTvethi-";
	int opt, optli;

	/* Initialize variables */
	fdin  = STDIN_FILENO;
	fdout = STDOUT_FILENO;
	for(i=0; i<OPTS_SIZE; i++)
		opts[i] = 0;

	/* Treat command line */
	opt = getopt_long(argc, argv, optstring, longOpts, &optli);
	while(opt != -1) {
		switch(opt) {
			case 'A':
			case 'E':
				opts[SHOWENDS] = 1;
				if(opt != 'A' && opt != 'e')
					break;

			case 't':
			case 'v':
				opts[NONV] = 1;
				if(opt != 'A' && opt != 'e' && opt != 't')
					break;

			case 'T':
				opts[SHOWT] = 1;
				break;

			case 'b':
				opts[NONB] = 1;
				break;

			case 'n':
				opts[NUM] = 1;
				break;

			case 's':
				opts[SQU] = 1;
				break;

			case 'h':
				show_help(argv);
				break;

			case 'i':
				cprint("\nVersion: %s", VERSION);
				cprint("\nBUG reports to: %s\n\n", BUG_REPORTS);
				break;
		}
		opt = getopt_long(argc, argv, optstring, longOpts, &optli);
	}

	/* Read files and write to output */
	if(argc == 1) {
		read_and_print(fdin, fdout, opts, 0);
	}

	lines = 0;
	for(i=1; i < argc; i++) {

		if(strlen(argv[i]) > 1 && argv[i][0] == '-')
			continue;

		/* Open file for read */
		if(strlen(argv[i]) == 1 && argv[i][0] == '-') {
			fdin = STDIN_FILENO;
		} else {
			if((fdin = open(argv[i], O_RDONLY)) < 0) {
				cprint("Could not read file %s\n", argv[i]);
			}
		}

		lines = read_and_print(fdin, fdout, opts, lines);

		/* If it's not the standard input, close it */
		if(fdin != STDIN_FILENO)
			close(fdin);
	}

	return(EXIT_SUCCESS);
}


/**
 * read_and_print
 * Read from fdin and write to fdout
 */
unsigned int read_and_print(int fdin,  int fdout, char opts[], unsigned int lines)
{
	unsigned int line, i;
	char buffer[BUFFER_SIZE];
	ssize_t count;
	char start, firstb;

	if(lines == 0) {
		line = 1;
	} else {
		line = lines;
	}

	start  = 1;
	firstb = 0;
	while((count=read(fdin, buffer, BUFFER_SIZE)) > 0) {

		for(i=0; i<count; i++) {

			/* Start-of-line */
			if(start) {
				if(opts[NUM] || (opts[NONB] && buffer[i] != '\n')) {
					cprint("%d ", line);
					line++;
				}

				if(opts[SQU] && buffer[i] == '\n') {
					if(!firstb) {
						firstb = 1;
					} else {
						start = 1;
						continue;
					}
				} else {
					firstb = 0;
				}

				start  = 0;
			}

			/* End-of-line */
			if(buffer[i] == '\n') {
				if(opts[SHOWENDS]) {
					cprint("$\n");
				} else {
					cprint("\n");
				}
				start = 1;
			} else {
				/* Middle of line */
				if(opts[SHOWT] && buffer[i] == '\t') {
						cprint("^I");
				} else {
					cprint("%c", buffer[i]);
				}
			}
		}

	}
	return(line);
}


/**
 * cprint
 * Print a message to standard output
 */
void cprint(const char *format, ...)
{
	char buffer[BUFFER_SIZE];
	int ssize;
	va_list ap;
	va_start(ap, format);

	ssize = vsnprintf(buffer, BUFFER_SIZE, format, ap);
	write(STDOUT_FILENO, buffer, ssize);

	va_end(ap);
}


/**
 * show_help
 * Show rcat help
 */
void show_help(char **argv)
{
	cprint("\nUsage: %s [OPTION] [FILE]...", argv[0]);
	cprint("\nConcatenate FILE(s), or standard input, to standard output.\n");

	cprint("\n  -A, --show-all           equivalent to -vET");
	cprint("\n  -b, --number-nonblank    number nonempty output lines");
	cprint("\n  -e                       equivalent to -vE");
	cprint("\n  -E, --show-ends          display $ at end of each line");
	cprint("\n  -n, --number             number all output lines");
	cprint("\n  -s, --squeeze-blank      suppress repeated empty output lines");
	cprint("\n  -t                       equivalent to -vT");
	cprint("\n  -T, --show-tabs          display TAB characters as ^I");
	/*cprint("\n  -v, --show-nonprinting   use ^ and M- notation, except for LFD and TAB");*/
	cprint("\n      --help     display this help and exit");
	cprint("\n      --version  output version information and exit\n");

	cprint("\nWith no FILE, or when FILE is -, read standard input.");
	cprint("\n");
}

